package com.atguigu.c_designed.m3beheviorModel.b2Command;

import java.util.ArrayList;
import java.util.List;

/**
 * 命令模式定义：
 * <p>
 * 命令模式：将一个请求封装为一个对象，从而让你可以用不同的请求对客户进行参数化，对请求排队或者记录请求日志，以及支持可撤销的操作。
 * 别名为动作(Action)模式或事务(Transaction)模式
 * “用不同的请求对客户进行参数化”
 * “对请求排队”
 * “记录请求日志”
 * “支持可撤销操作”
 */
public class CommandPatternClient {
    public static void main(String[] args) {
        Receiver lightreceiver = new LightReceiver();
        Receiver tvReceiver = new TVReceiver();
        Command ligthOnCommand = new LigthOnCommand(lightreceiver);
        Command ligthOffCommand = new LigthOffCommand(lightreceiver);
        Command tvOnCommand = new TVOnCommand(tvReceiver);
        Command tvOffCommand = new TVOffCommand(tvReceiver);
        Invoker invoker = new Invoker();
        invoker.setOnCommands(0,ligthOnCommand);
        invoker.setOffCommands(0,ligthOffCommand);
        invoker.setOnCommands(1,tvOnCommand);
        invoker.setOffCommands(1,tvOffCommand);
//        System.out.println("发送开灯命令");
//        invoker.executeOnCommand(0);
//        System.out.println("发送关灯命令");
//        invoker.executeOffCommand(0);

//        System.out.println("发送开TV命令");
//        invoker.executeOnCommand(1);
        System.out.println("发送关TV命令");
        invoker.executeOffCommand(1);
        System.out.println("撤销当前命令");
        invoker.executeUndoCommand();
    }
}

/**
 * 命令模式的本质是对命令进行封装，将发出命令的责任和执行命令的责任分割开。
 * 每一个命令都是一个操作：请求的一方发出请求，要求执行一个操作；接收的一方收到请求，并执行操作。
 * 命令模式允许请求的一方和接收的一方独立开来，使得请求的一方不必知道接收请求的一方的接口，更不必知道请求是怎么被接收，以及操作是否被执行、何时被执行，以及是怎么被执行的。
 * 命令模式使请求本身成为一个对象，这个对象和其他对象一样可以被存储和传递。
 * 命令模式的关键在于引入了抽象命令接口，且发送者针对抽象命令接口编程，只有实现了抽象命令接口的具体命令才能与接收者相关联。
 */

/**
 * 6.3.4 优缺点
 * 1，优点：
 *
 * - 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
 * - 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类，它满足“开闭原则”，对扩展比较灵活。
 * - 可以实现宏命令。命令模式可以与组合模式结合，将多个命令装配成一个组合命令，即宏命令。
 * - 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合，实现命令的撤销与恢复。
 *
 * 2，缺点：
 * - 使用命令模式可能会导致某些系统有过多的具体命令类。
 * - 系统结构更加复杂。
 *
 * 6.3.5 使用场景
 *
 * - 系统需要将请求调用者和请求接收者解耦，使得调用者和接收者不直接交互。
 * - 系统需要在不同的时间指定请求、将请求排队和执行请求。
 * - 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
 */
abstract class Receiver{
    public abstract void on();
    public abstract void off();
}

// 接收者执行与请求相关的操作，它具体实现对请求的业务处理。
class LightReceiver extends Receiver{
    public void on() {
        System.out.println("执行打开命令。。。");
    }
    public void off() {
        System.out.println("执行关闭命令...");
    }
}
class TVReceiver extends Receiver {
    public void on() {
        System.out.println("执行打开TV命令。。。");
    }
    public void off() {
        System.out.println("执行关闭TV命令...");
    }
    public void change() {
        System.out.println("执行换台命令...");
    }
}
//抽象命令类一般是一个接口，在其中声明了用于执行请求的execute()等方法，通过这些方法可以调用请求接受者的相关操作。
abstract class Command {
    Receiver receiver;

    public Command(Receiver receiver) {
        this.receiver = receiver;
    }
    public abstract void execute();

    public abstract void undo();
}

// 具体命令类是抽象命令类的子类，实现了在抽象命令类中声明的方法，它对应具体的接收对象，
// 绑定接收者对象的动作。在实现execute方法时，将调用接收者对象的相关操作（Action）。
class LigthOnCommand extends Command {
    public LigthOnCommand(Receiver receiver) {
        super(receiver);
    }

    @Override
    public void undo() {
        receiver.off();
    }

    @Override
    public void execute() {
        receiver.on();
    }
}

class LigthOffCommand extends Command {

    public LigthOffCommand(Receiver receiver) {
       super(receiver);
    }

    @Override
    public void execute() {
        receiver.off();
    }

    @Override
    public void undo() {
        receiver.on();
    }
}
class TVOnCommand extends Command {
    public TVOnCommand(Receiver receiver) {
        super(receiver);
    }
    @Override
    public void execute() {
        receiver.on();
    }

    @Override
    public void undo() {
        receiver.off();
    }
}
class TVOffCommand extends Command {
    public TVOffCommand(Receiver receiver) {
        super(receiver);
    }

    @Override
    public void undo() {
        receiver.on();
    }

    @Override
    public void execute() {
        receiver.off();
    }
}

// 调用者即请求的发送者，又称为请求者，它通过命令对象来执行请求。
// 一个调用者并不需要在设计时确定其接收者，因此它只与抽象命令类存在关联关系。在程序运行时将调用具体命令对象的execute方法，间接调用接受者的相关操作。
class Invoker {

    List<Command> onCommands=new ArrayList<Command>();
    List<Command> offCommands=new ArrayList<Command>();
    List<Command> commands=new ArrayList<Command>();

    public void setOnCommands(int index, Command onCommands) {
        this.onCommands.add(index, onCommands);
    }

    public void setOffCommands(int index, Command offCommands) {
        this.offCommands.add(index, offCommands);
    }

    public void executeOnCommand(int index) {
        Command command = onCommands.get(index);
        command.execute();
        commands.add(command);
    }
    public void executeOffCommand(int index) {
        Command command = offCommands.get(index);
        command.execute();
        commands.add(command);
    }
    public void executeUndoCommand() {
        if (commands.size()>0){
            Command command = commands.remove(commands.size()-1);
            command.undo();
        }else {
            System.out.println("无命令可执行");
        }

    }
}